inspector: Add an fps overlay
authorBenjamin Otte <otte@redhat.com>
Thu, 5 Apr 2018 10:58:46 +0000 (12:58 +0200)
committerBenjamin Otte <otte@redhat.com>
Thu, 5 Apr 2018 12:58:43 +0000 (14:58 +0200)
gtk/inspector/fpsoverlay.c [new file with mode: 0644]
gtk/inspector/fpsoverlay.h [new file with mode: 0644]
gtk/inspector/meson.build
gtk/inspector/visual.c
gtk/inspector/visual.ui

diff --git a/gtk/inspector/fpsoverlay.c b/gtk/inspector/fpsoverlay.c
new file mode 100644 (file)
index 0000000..a376409
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "fpsoverlay.h"
+
+#include "gtkintl.h"
+#include "gtkwidget.h"
+#include "gtkwindow.h"
+
+/* duration before we start fading in us */
+#define GDK_FPS_OVERLAY_LINGER_DURATION (1000 * 1000)
+/* duration when fade is finished in us */
+#define GDK_FPS_OVERLAY_FADE_DURATION (500 * 1000)
+
+typedef struct _GtkFpsInfo {
+  gint64 last_frame;
+  GskRenderNode *last_node;
+} GtkFpsInfo;
+
+struct _GtkFpsOverlay
+{
+  GtkInspectorOverlay parent_instance;
+
+  GHashTable *infos; /* GtkWidget => GtkFpsInfo */
+};
+
+struct _GtkFpsOverlayClass
+{
+  GtkInspectorOverlayClass parent_class;
+};
+
+G_DEFINE_TYPE (GtkFpsOverlay, gtk_fps_overlay, GTK_TYPE_INSPECTOR_OVERLAY)
+
+static void
+gtk_fps_info_free (gpointer data)
+{
+  GtkFpsInfo *info = data;
+
+  gsk_render_node_unref (info->last_node);
+
+  g_slice_free (GtkFpsInfo, info);
+}
+
+static gint64
+guess_refresh_interval (GdkFrameClock *frame_clock)
+{
+  gint64 interval;
+  gint64 i;
+
+  interval = G_MAXINT64;
+
+  for (i = gdk_frame_clock_get_history_start (frame_clock);
+       i < gdk_frame_clock_get_frame_counter (frame_clock);
+       i++)
+    {
+      GdkFrameTimings *t, *before;
+      gint64 ts, before_ts;
+
+      t = gdk_frame_clock_get_timings (frame_clock, i);
+      before = gdk_frame_clock_get_timings (frame_clock, i - 1);
+      if (t == NULL || before == NULL)
+        continue;
+
+      ts = gdk_frame_timings_get_frame_time (t);
+      before_ts = gdk_frame_timings_get_frame_time (before);
+      if (ts == 0 || before_ts == 0)
+        continue;
+
+      interval = MIN (interval, ts - before_ts);
+    }
+
+  if (interval == G_MAXINT64)
+    return 0;
+
+  return interval;
+}
+
+static double
+gtk_fps_overlay_get_fps (GtkWidget *widget)
+{
+  GdkFrameClock *frame_clock;
+  GdkFrameTimings *start, *end;
+  gint64 start_counter, end_counter;
+  gint64 start_timestamp, end_timestamp;
+  gint64 interval;
+
+  frame_clock = gtk_widget_get_frame_clock (widget);
+  if (frame_clock == NULL)
+    return 0.0;
+
+  start_counter = gdk_frame_clock_get_history_start (frame_clock);
+  end_counter = gdk_frame_clock_get_frame_counter (frame_clock);
+  start = gdk_frame_clock_get_timings (frame_clock, start_counter);
+  for (end = gdk_frame_clock_get_timings (frame_clock, end_counter);
+       end_counter > start_counter && end != NULL && !gdk_frame_timings_get_complete (end);
+       end = gdk_frame_clock_get_timings (frame_clock, end_counter))
+    end_counter--;
+  if (end_counter - start_counter < 4)
+    return 0.0;
+
+  start_timestamp = gdk_frame_timings_get_presentation_time (start);
+  end_timestamp = gdk_frame_timings_get_presentation_time (end);
+  if (start_timestamp == 0 || end_timestamp == 0)
+    {
+      start_timestamp = gdk_frame_timings_get_frame_time (start);
+      end_timestamp = gdk_frame_timings_get_frame_time (end);
+    }
+
+  interval = gdk_frame_timings_get_refresh_interval (end);
+  if (interval == 0)
+    {
+      interval = guess_refresh_interval (frame_clock);
+      if (interval == 0)
+        return 0.0;
+    }
+
+  return ((double) end_counter - start_counter) * G_USEC_PER_SEC / (end_timestamp - start_timestamp);
+}
+
+static gboolean
+gtk_fps_overlay_force_redraw (GtkWidget     *widget,
+                              GdkFrameClock *clock,
+                              gpointer       unused)
+{
+  gdk_surface_queue_expose (gtk_widget_get_surface (widget));
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gtk_fps_overlay_snapshot (GtkInspectorOverlay *overlay,
+                          GtkSnapshot         *snapshot,
+                          GskRenderNode       *node,
+                          GtkWidget           *widget)
+{
+  GtkFpsOverlay *self = GTK_FPS_OVERLAY (overlay);
+  GtkFpsInfo *info;
+  PangoLayout *layout;
+  gint64 now;
+  double fps;
+  char *fps_string;
+  graphene_rect_t bounds;
+  int width, height;
+  double overlay_opacity;
+
+  now = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget));
+  info = g_hash_table_lookup (self->infos, widget);
+  if (info == NULL)
+    {
+      info = g_slice_new0 (GtkFpsInfo);
+      g_hash_table_insert (self->infos, widget, info);
+    }
+  if (info->last_node != node)
+    {
+      g_clear_pointer (&info->last_node, gsk_render_node_unref);
+      info->last_node = gsk_render_node_ref (node);
+      info->last_frame = now;
+      overlay_opacity = 1.0;
+    }
+  else
+    {
+      if (now - info->last_frame > GDK_FPS_OVERLAY_LINGER_DURATION + GDK_FPS_OVERLAY_FADE_DURATION)
+        {
+          g_hash_table_remove (self->infos, widget);
+          return;
+        }
+      else if (now - info->last_frame > GDK_FPS_OVERLAY_LINGER_DURATION)
+        {
+          overlay_opacity = 1.0 - (double) (now - info->last_frame - GDK_FPS_OVERLAY_LINGER_DURATION)
+                                  / GDK_FPS_OVERLAY_FADE_DURATION;
+        }
+      else
+        {
+          overlay_opacity = 1.0;
+        }
+    }
+
+  fps = gtk_fps_overlay_get_fps (widget);
+  if (fps == 0.0)
+    fps_string = g_strdup ("--- fps");
+  else
+    fps_string = g_strdup_printf ("%.2f fps", fps);
+
+  if (GTK_IS_WINDOW (widget))
+    {
+      GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
+      if (child)
+        gtk_widget_compute_bounds (child, widget, &bounds);
+      else
+        gtk_widget_compute_bounds (widget, widget, &bounds);
+    }
+  else
+    {
+      gtk_widget_compute_bounds (widget, widget, &bounds);
+    }
+
+  layout = gtk_widget_create_pango_layout (widget, fps_string);
+  pango_layout_get_pixel_size (layout, &width, &height);
+
+  gtk_snapshot_offset (snapshot, bounds.origin.x + bounds.size.width - width, bounds.origin.y);
+  if (overlay_opacity < 1.0)
+    gtk_snapshot_push_opacity (snapshot, overlay_opacity, "Fps Overlay Opacity: %g", overlay_opacity);
+  gtk_snapshot_append_color (snapshot,
+                             &(GdkRGBA) { 0, 0, 0, 0.5 },
+                             &GRAPHENE_RECT_INIT (-1, -1, width + 2, height + 2),
+                             "Fps Overlay background");
+  gtk_snapshot_append_layout (snapshot,
+                              layout,
+                              &(GdkRGBA) { 1, 1, 1, 1 },
+                              "Fps Overlay: %s", fps_string);
+  if (overlay_opacity < 1.0)
+    gtk_snapshot_pop (snapshot);
+  gtk_snapshot_offset (snapshot, - bounds.origin.x - bounds.size.width + width, - bounds.origin.y);
+  g_free (fps_string);
+
+  gtk_widget_add_tick_callback (widget, gtk_fps_overlay_force_redraw, NULL, NULL);
+}
+
+static void
+gtk_fps_overlay_queue_draw (GtkInspectorOverlay *overlay)
+{
+  GtkFpsOverlay *self = GTK_FPS_OVERLAY (overlay);
+  GHashTableIter iter;
+  gpointer widget;
+
+  g_hash_table_iter_init (&iter, self->infos);
+  while (g_hash_table_iter_next (&iter, &widget, NULL))
+    gdk_surface_queue_expose (gtk_widget_get_surface (widget));
+}
+
+static void
+gtk_fps_overlay_dispose (GObject *object)
+{
+  GtkFpsOverlay *self = GTK_FPS_OVERLAY (object);
+
+  g_hash_table_unref (self->infos);
+
+  G_OBJECT_CLASS (gtk_fps_overlay_parent_class)->dispose (object);
+}
+
+static void
+gtk_fps_overlay_class_init (GtkFpsOverlayClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GtkInspectorOverlayClass *overlay_class = GTK_INSPECTOR_OVERLAY_CLASS (klass);
+
+  overlay_class->snapshot = gtk_fps_overlay_snapshot;
+  overlay_class->queue_draw = gtk_fps_overlay_queue_draw;
+
+  gobject_class->dispose = gtk_fps_overlay_dispose;
+}
+
+static void
+gtk_fps_overlay_init (GtkFpsOverlay *self)
+{
+  self->infos = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, gtk_fps_info_free);
+}
+
+GtkInspectorOverlay *
+gtk_fps_overlay_new (void)
+{
+  GtkFpsOverlay *self;
+
+  self = g_object_new (GTK_TYPE_FPS_OVERLAY, NULL);
+
+  return GTK_INSPECTOR_OVERLAY (self);
+}
+
diff --git a/gtk/inspector/fpsoverlay.h b/gtk/inspector/fpsoverlay.h
new file mode 100644 (file)
index 0000000..2def5cd
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __GTK_FPS_OVERLAY_H__
+#define __GTK_FPS_OVERLAY_H__
+
+#include "inspectoroverlay.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_FPS_OVERLAY (gtk_fps_overlay_get_type ())
+G_DECLARE_FINAL_TYPE (GtkFpsOverlay, gtk_fps_overlay, GTK, FPS_OVERLAY, GtkInspectorOverlay)
+
+GtkInspectorOverlay *   gtk_fps_overlay_new                     (void);
+
+G_END_DECLS
+
+#endif /* __GTK_FPS_OVERLAY_H__ */
index 585855c4228153ee2bd08f6e8ecb693d855ac467..3f241585f5a08a838dfebaa8b6caecc6739ec0ab 100644 (file)
@@ -5,6 +5,7 @@ inspector_sources = files(
   'css-editor.c',
   'css-node-tree.c',
   'data-list.c',
+  'fpsoverlay.c',
   'general.c',
   'gestures.c',
   'graphdata.c',
index 4af9b9a8a2c128b69f58df2e2221af1a7eff04db..c2ea6aca11c7d1c6fe8a435c62f94cef8762141a 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "visual.h"
 
+#include "fpsoverlay.h"
 #include "updatesoverlay.h"
 #include "window.h"
 
@@ -68,7 +69,6 @@ struct _GtkInspectorVisualPrivate
 
   GtkWidget *debug_box;
   GtkWidget *rendering_mode_combo;
-  GtkWidget *updates_switch;
   GtkWidget *baselines_switch;
   GtkWidget *layout_switch;
   GtkWidget *touchscreen_switch;
@@ -80,6 +80,7 @@ struct _GtkInspectorVisualPrivate
 
   GtkAdjustment *focus_adjustment;
 
+  GtkInspectorOverlay *fps_overlay;
   GtkInspectorOverlay *updates_overlay;
 };
 
@@ -226,6 +227,41 @@ font_scale_entry_activated (GtkEntry           *entry,
     update_font_scale (vis, factor, TRUE, FALSE);
 }
 
+static void
+fps_activate (GtkSwitch          *sw,
+              GParamSpec         *pspec,
+              GtkInspectorVisual *vis)
+{
+  GtkInspectorVisualPrivate *priv = vis->priv;
+  GtkInspectorWindow *iw;
+  gboolean fps;
+
+  fps = gtk_switch_get_active (sw);
+  iw = GTK_INSPECTOR_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (vis)));
+  if (iw == NULL)
+    return;
+
+  if (fps)
+    {
+      if (priv->fps_overlay == NULL)
+        {
+          priv->fps_overlay = gtk_fps_overlay_new ();
+          gtk_inspector_window_add_overlay (iw, priv->fps_overlay);
+          g_object_unref (priv->fps_overlay);
+        }
+    }
+  else
+    {
+      if (priv->fps_overlay != NULL)
+        {
+          gtk_inspector_window_remove_overlay (iw, priv->fps_overlay);
+          priv->fps_overlay = NULL;
+        }
+    }
+
+  redraw_everything ();
+}
+
 static void
 updates_activate (GtkSwitch          *sw,
                   GParamSpec         *pspec,
@@ -901,7 +937,6 @@ gtk_inspector_visual_class_init (GtkInspectorVisualClass *klass)
 
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/visual.ui");
   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, rendering_mode_combo);
-  gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, updates_switch);
   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, direction_combo);
   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, baselines_switch);
   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, layout_switch);
@@ -927,6 +962,7 @@ gtk_inspector_visual_class_init (GtkInspectorVisualClass *klass)
   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, font_scale_entry);
   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, font_scale_adjustment);
 
+  gtk_widget_class_bind_template_callback (widget_class, fps_activate);
   gtk_widget_class_bind_template_callback (widget_class, updates_activate);
   gtk_widget_class_bind_template_callback (widget_class, direction_changed);
   gtk_widget_class_bind_template_callback (widget_class, rendering_mode_changed);
index 14433754b61002917e8dfea95fbcff3a4564f571..4cd7913bf4d43206b9b2f6e2555038814d567895 100644 (file)
                     </child>
                   </object>
                 </child>
+                <child>
+                  <object class="GtkListBoxRow">
+                    <property name="activatable">0</property>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="margin">10</property>
+                        <property name="spacing">40</property>
+                        <child>
+                          <object class="GtkLabel" id="fps_label">
+                            <property name="label" translatable="yes">Show fps overlay</property>
+                            <property name="halign">start</property>
+                            <property name="valign">baseline</property>
+                            <property name="xalign">0.0</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkSwitch" id="fps_switch">
+                            <property name="halign">end</property>
+                            <property name="valign">baseline</property>
+                            <property name="hexpand">1</property>
+                            <signal name="notify::active" handler="fps_activate"/>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
                 <child>
                   <object class="GtkListBoxRow">
                     <property name="activatable">0</property>